import uuid
import base64
import os
import asyncio
import math
import io
import re
from datetime import datetime
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, BackgroundTasks, Query
from typing import List, Optional
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from openai import OpenAI
from markitdown import MarkItDown

from app import crud, schemas
from app.api import deps
from app.core.config import settings
from app.core.logging_config import get_logger
from app.models import User
from app.schemas import UploadedFileResponse, FileCountResponse, FileListResponse, FileListItem, BatchUploadResponse
from app.schemas.base import UnifiedResponse, success_response
from app.utils.medical_extraction import extract_medical_info_async
from app.utils.minio_client import get_minio_client

router = APIRouter()
logger = get_logger(__name__)

llm_client: Optional[OpenAI] = None
md: Optional[MarkItDown] = None


def get_markitdown() -> Optional[MarkItDown]:
    """Lazily initialize MarkItDown client when parsing is required."""
    global llm_client, md

    if md is not None:
        return md

    if not settings.OPENAI_API_KEY:
        return None

    llm_client = OpenAI(
        api_key=settings.OPENAI_API_KEY,
        base_url=settings.OPENAI_BASE_URL
    )
    md = MarkItDown(llm_client=llm_client, llm_model=settings.MARKITDOWN_MODEL)
    return md


async def _get_public_upload_user(db: AsyncSession) -> User:
    phone = settings.PUBLIC_UPLOAD_USER_PHONE
    if not phone:
        raise HTTPException(status_code=500, detail="Public upload user not configured")

    public_user = await crud.user.get_by_phone(db, phone=phone)
    if not public_user:
        raise HTTPException(status_code=500, detail="Public upload user not found")

    return public_user


@router.post("/upload", response_model=UnifiedResponse[BatchUploadResponse])
async def upload_files(
        background_tasks: BackgroundTasks,
        files: List[UploadFile] = File(...),
        file_type: int = Query(2, description="文件类型: 1=医疗档案, 2=普通文件"),
        directory: str = Query(None, description="上传目录路径，例如: documents/reports"),
        upload_time: str = Query(None, description="上传时间，ISO格式字符串，例如: 2024-01-01T12:00:00"),
        db: AsyncSession = Depends(deps.get_db),
        current_user: User = Depends(deps.get_current_user)
):
    """
    批量上传文件到MinIO，解析内容，并创建数据库记录

    - **files**: 上传的文件列表 (multipart/form-data)
    - **file_type**: 文件类型，1=医疗档案(需要信息提取), 2=普通文件
    - **directory**: 可选的目录路径，如果不提供则使用默认的用户目录结构
    - **upload_time**: 上传时间，ISO格式字符串，如果不提供则使用当前时间
    """
    if not files:
        raise HTTPException(status_code=400, detail="No files provided.")

    uploaded_files = []
    failed_files = []


    # 处理上传时间：前端传入则解析，否则使用当前时间
    if upload_time:
        try:
            # 尝试解析ISO格式的时间字符串
            batch_upload_time = datetime.fromisoformat(upload_time.replace('Z', '+00:00'))
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid upload_time format. Expected ISO format like '2024-01-01T12:00:00' or '2024-01-01T12:00:00Z'")
    else:
        # 使用当前时间作为默认值
        batch_upload_time = datetime.now()

    for file in files:
        try:
            # 处理单个文件
            uploaded_file = await _process_single_file(
                file, file_type, directory, current_user, db, background_tasks, batch_upload_time
            )
            uploaded_files.append(uploaded_file)
        except Exception as e:
            logger.error(f"Failed to process file {file.filename}: {e}")
            failed_files.append(file.filename)
            continue
    
    return success_response(BatchUploadResponse(
        uploaded_files=uploaded_files,
        total_count=len(files),
        success_count=len(uploaded_files),
        failed_count=len(failed_files),
        failed_files=failed_files
    ))


@router.post("/upload/public", response_model=UnifiedResponse[BatchUploadResponse])
async def upload_public_files(
    background_tasks: BackgroundTasks,
    files: List[UploadFile] = File(...),
    file_type: int = Query(2, description="文件类型: 1=医疗档案, 2=普通文件"),
    directory: str = Query(None, description="上传目录路径，例如: documents/reports"),
    upload_time: str = Query(None, description="上传时间，ISO格式字符串，例如: 2024-01-01T12:00:00"),
    purpose: str = Query("public", description="上传用途标识，用于区分存储目录"),
    db: AsyncSession = Depends(deps.get_db),
):
    """
    免登录批量上传文件，仅保留原有上传逻辑，不做内容解析。

    - **files**: 上传的文件列表 (multipart/form-data)
    - **file_type**: 文件类型，1=医疗档案, 2=普通文件
    - **directory**: 可选上传目录
    - **upload_time**: 上传时间（ISO格式字符串）
    - **purpose**: 目录用途标识
    """
    if not files:
        raise HTTPException(status_code=400, detail="No files provided.")

    uploaded_files = []
    failed_files = []

    if upload_time:
        try:
            batch_upload_time = datetime.fromisoformat(upload_time.replace('Z', '+00:00'))
        except ValueError:
            raise HTTPException(status_code=400, detail="Invalid upload_time format. Expected ISO format like '2024-01-01T12:00:00' or '2024-01-01T12:00:00Z'")
    else:
        batch_upload_time = datetime.now()

    public_user = await _get_public_upload_user(db)

    for file in files:
        try:
            uploaded_file = await _process_single_file(
                file=file,
                file_type=file_type,
                directory=directory,
                current_user=public_user,
                db=db,
                background_tasks=background_tasks,
                batch_upload_time=batch_upload_time,
                purpose=purpose,
                allow_medical_record=False,
                should_parse=False,
            )
            uploaded_files.append(uploaded_file)
        except Exception as e:
            logger.error(f"Failed to process file {file.filename}: {e}")
            failed_files.append(file.filename)

    return success_response(BatchUploadResponse(
        uploaded_files=uploaded_files,
        total_count=len(files),
        success_count=len(uploaded_files),
        failed_count=len(failed_files),
        failed_files=failed_files
    ))


@router.post(
    "/upload/temporary",
    response_model=UnifiedResponse[UploadedFileResponse]
)
async def upload_temporary_file(
    background_tasks: BackgroundTasks,
    file: UploadFile = File(...),
    file_type: int = Query(3, description="文件类型，仅支持执业资质材料(3)或普通文件(2)"),
    purpose: str = Query("registration", description="上传用途，比如 registration"),
    db: AsyncSession = Depends(deps.get_db),
):
    """
    临时文件上传接口，用于未登录场景（如注册流程中的证件上传）

    - **file**: 单个上传文件
    - **file_type**: 允许的文件类型（默认仅证件材料3）
    - **purpose**: 用途标记，用于区分存储目录
    """
    if file_type not in (2, 3):
        raise HTTPException(status_code=400, detail="临时上传仅支持普通文件或执业资质材料")

    batch_upload_time = datetime.now()
    public_user = await _get_public_upload_user(db)
    uploaded_file = await _process_single_file(
        file=file,
        file_type=file_type,
        directory=None,
        current_user=public_user,
        db=db,
        background_tasks=background_tasks,
        batch_upload_time=batch_upload_time,
        purpose=purpose,
        allow_medical_record=False,
        should_parse=False,
    )
    return success_response(uploaded_file, msg="文件上传成功")


async def _process_single_file(
    file: UploadFile,
    file_type: int,
    directory: str,
    current_user: Optional[User],
    db: AsyncSession,
    background_tasks: BackgroundTasks,
    batch_upload_time: datetime,
    purpose: str = "user",
    allow_medical_record: bool = True,
    should_parse: bool = True,
) -> UploadedFileResponse:
    """
    处理单个文件的上传逻辑
    """
    if not file.filename:
        raise HTTPException(status_code=400, detail="No file name provided.")

    file_extension = os.path.splitext(file.filename)[1]
    unique_filename = f"{uuid.uuid4()}{file_extension}"
    owner_id = current_user.id if current_user else 'ef1d2692-0829-4eb4-b2f1-d48b3b8a0993'
    sanitized_purpose = re.sub(r"[^a-zA-Z0-9_-]", "", purpose.lower()) or "general"

    upload_directory = directory
    if not upload_directory:
        current_date = batch_upload_time.strftime("%Y/%m")
        if owner_id:
            base_directory = f"users/{owner_id}"
        else:
            base_directory = f"temporary/{sanitized_purpose}"

        if file.content_type and file.content_type.startswith("image/"):
            upload_directory = f"{base_directory}/images/{current_date}"
        elif file.content_type and "document" in file.content_type:
            upload_directory = f"{base_directory}/documents/{current_date}"
        elif file.content_type and file.content_type.startswith("application/pdf"):
            upload_directory = f"{base_directory}/pdf/{current_date}"
        else:
            upload_directory = f"{base_directory}/files/{current_date}"

    try:
        content = await file.read()
        file_size = len(content)

        minio_client = get_minio_client()
        file_stream = io.BytesIO(content)

        object_name = await minio_client.upload_file(
            object_name=unique_filename,
            file_data=file_stream,
            content_type=file.content_type,
            file_size=file_size,
            directory=upload_directory,
        )

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Could not save file to MinIO: {e}")

    parsed_content = None
    if should_parse and file.content_type and not file.content_type.startswith("image/"):
        md_client = get_markitdown()
        if md_client:
            try:
                import tempfile

                with tempfile.NamedTemporaryFile(suffix=file_extension, delete=False) as tmp_file:
                    tmp_file.write(content)
                    tmp_file.flush()
                    result = md_client.convert(tmp_file.name)
                    parsed_content = result.text_content
                    os.unlink(tmp_file.name)
            except Exception as e:
                logger.warning(f"Could not parse file {file.filename}: {e}")

    file_in = schemas.UploadedFileCreate(
        user_id=owner_id,
        filename=file.filename,
        file_path=object_name,
        content_type=file.content_type or "",
        file_size=file_size,
        file_type=file_type,
        parsed_content=parsed_content,
    )
    db_file = await crud.crud_file.create(db, obj_in=file_in)

    await db.commit()
    await db.refresh(db_file)

    medical_record = None
    if allow_medical_record and file_type == 1 and owner_id:
        medical_record_in = schemas.MedicalRecordCreate(
            user_id=owner_id,
            file_id=db_file.id,
            extraction_status="pending",
            upload_time=batch_upload_time,
        )
        medical_record = await crud.crud_medical_record.create(db, obj_in=medical_record_in)
        await db.commit()
        await db.refresh(medical_record)

        if parsed_content and parsed_content.strip():
            background_tasks.add_task(
                extract_medical_info_async,
                parsed_content,
                str(medical_record.id),
            )
        elif file.content_type and file.content_type.startswith("image/"):
            image_url = f"{settings.MINIO_ENDPOINT}/{settings.MINIO_BUCKET_NAME}/{object_name}"
            background_tasks.add_task(
                extract_medical_info_async,
                "",
                str(medical_record.id),
                image_url,
            )

    return UploadedFileResponse(
        file_id=db_file.id,
        filename=db_file.filename,
        file_type=db_file.file_type,
        file_path=db_file.file_path,
        file_size=db_file.file_size,
        content=db_file.parsed_content,
    )

@router.get("/count", response_model=UnifiedResponse[FileCountResponse])
async def get_file_count(
        db: AsyncSession = Depends(deps.get_db),
        current_user: User = Depends(deps.get_current_user)
):
    """
    获取当前登录用户上传的文件个数
    
    Returns the total count of files uploaded by the current user
    """
    count = await crud.crud_file.count_by_user(db, user_id=current_user.id)
    return success_response(FileCountResponse(count=count))


@router.get("/list", response_model=UnifiedResponse[FileListResponse])
async def get_user_files(
        page: int = Query(1, ge=1, description="页码，从1开始"),
        page_size: int = Query(20, ge=1, le=100, description="每页大小，最大100"),
        db: AsyncSession = Depends(deps.get_db),
        current_user: User = Depends(deps.get_current_user)
):
    """
    获取当前登录用户上传的文件列表（分页）
    
    - **page**: 页码，从1开始
    - **page_size**: 每页大小，默认20，最大100
    """
    files, total = await crud.crud_file.get_user_files_paginated(
        db, user_id=current_user.id, page=page, page_size=page_size
    )
    
    # 计算总页数
    total_pages = math.ceil(total / page_size) if total > 0 else 0
    
    # 转换为响应格式
    file_items = [
        FileListItem(
            file_id=file.id,
            filename=file.filename,
            content_type=file.content_type,
            file_size=file.file_size,
            file_type=file.file_type,
            created_at=file.created_at
        )
        for file in files
    ]
    
    return success_response(FileListResponse(
        items=file_items,
        total=total,
        page=page,
        page_size=page_size,
        total_pages=total_pages
    ))


@router.get("/{file_id}", response_model=UnifiedResponse[UploadedFileResponse])
async def get_file_info(
        file_id: str,
        db: AsyncSession = Depends(deps.get_db),
        current_user: User = Depends(deps.get_current_user)
):
    """
    获取文件信息，包括医疗报告提取的结果
    
    - **file_id**: 文件ID
    """
    db_file = await crud.crud_file.get(db, id=file_id)
    if not db_file:
        raise HTTPException(status_code=404, detail="File not found")
    
    # 检查文件是否属于当前用户
    if db_file.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="Access denied")
    
    return success_response(UploadedFileResponse(
        file_id=db_file.id,
        filename=db_file.filename,
        file_type=db_file.file_type,
        file_path=db_file.file_path,
        file_size=db_file.file_size,
        content=db_file.parsed_content
    ))


@router.get("/{file_id}/download")
async def download_file(
        file_id: str,
        db: AsyncSession = Depends(deps.get_db),
        current_user: User = Depends(deps.get_current_user)
):
    """
    下载文件
    
    - **file_id**: 文件ID
    """
    db_file = await crud.crud_file.get(db, id=file_id)
    if not db_file:
        raise HTTPException(status_code=404, detail="File not found")
    
    # 检查文件是否属于当前用户
    if db_file.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="Access denied")
    
    try:
        # 从MinIO下载文件
        minio_client = get_minio_client()
        file_data = await minio_client.download_file(db_file.file_path)
        
        # 返回文件流
        return StreamingResponse(
            io.BytesIO(file_data), 
            media_type=db_file.content_type or "application/octet-stream",
            headers={"Content-Disposition": f"attachment; filename={db_file.filename}"}
        )
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to download file: {e}")


@router.get("/{file_id}/url")
async def get_file_url(
        file_id: str,
        expires: int = Query(3600, description="URL过期时间（秒），默认1小时"),
        db: AsyncSession = Depends(deps.get_db),
        current_user: User = Depends(deps.get_current_user)
):
    """
    获取文件的预签名URL
    
    - **file_id**: 文件ID
    - **expires**: URL过期时间（秒），默认1小时
    """
    db_file = await crud.crud_file.get(db, id=file_id)
    if not db_file:
        raise HTTPException(status_code=404, detail="File not found")
    
    # 检查文件是否属于当前用户
    if db_file.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="Access denied")
    
    try:
        # 获取MinIO预签名URL
        minio_client = get_minio_client()
        file_url = await minio_client.get_file_url(db_file.file_path, expires)
        
        return success_response({"url": file_url, "expires": expires})
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to generate file URL: {e}")
